package rexsee.network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import rexsee.core.utilities.Json;
import rexsee.network.SocketAbstractClass.SocketConnectedListener;
import rexsee.network.SocketAbstractClass.SocketDataListener;
import rexsee.network.SocketAbstractClass.SocketFailedListener;
import rexsee.network.SocketAbstractClass.SocketLostListener;
import rexsee.network.SocketAbstractClass.SocketStartedListener;

public class SocketServer extends Thread {

	private final String mServerId;
	private final InetSocketAddress mLocalInetSocketAddress;
	private final int mBacklog;
	private final int mTimeout;
	private final int mReadingCycle;
	private SocketStartedListener mOnStarted;
	private SocketFailedListener mOnFailed;
	private SocketConnectedListener mOnConnected;
	private SocketLostListener mOnLost = null;
	private SocketDataListener mOnRead = null;
	private SocketDataListener mOnWrite = null;
	private ServerSocket mServerSocket;
	private HashMap<Long, Socket> mSockets;
	private HashMap<Long, Thread> mReadingThreads;

	public SocketServer(String serverId, InetSocketAddress localInetSocketAddress, int backlog, int timeout, int readingCycle) {
		super();
		mServerId = serverId;
		mLocalInetSocketAddress = localInetSocketAddress;
		mBacklog = backlog;
		mTimeout = timeout;
		mReadingCycle = readingCycle;
		mSockets = new HashMap<Long, Socket>();
		mReadingThreads = new HashMap<Long, Thread>();
	}

	@Override
	public void run() {
		try {
			mServerSocket = new ServerSocket(mLocalInetSocketAddress.getPort(), mBacklog, mLocalInetSocketAddress.getAddress());
			if (mTimeout > 0) mServerSocket.setSoTimeout(mTimeout);
		} catch (Exception e) {
			close();
			if (mOnFailed != null) mOnFailed.run(mServerId, true, e.getLocalizedMessage());
		}
		if (mOnStarted != null) mOnStarted.run(mServerId, true);
		while (mServerSocket != null) {
			Socket waitingSocket = null;
			try {
				waitingSocket = mServerSocket.accept();
			} catch (Exception e) {
				close();
				if (mOnFailed != null) mOnFailed.run(mServerId, true, e.getLocalizedMessage());
				break;
			}
			if (waitingSocket != null) {
				final Long index = System.currentTimeMillis();
				final Socket socket = waitingSocket;
				Thread thread = new Thread() {
					@Override
					public void run() {
						try {
							InputStream inputStream = socket.getInputStream();
							while (socket != null) {
								try {
									int length = inputStream.available();
									if (length == 0) {
										Thread.sleep(mReadingCycle);
									} else {
										byte[] buffer = new byte[length];
										inputStream.read(buffer);
										if (mOnRead != null) mOnRead.run(mServerId, index, buffer);
									}
								} catch (Exception e) {
									//close();
									if (mOnLost != null) mOnLost.run(mServerId, index, e.getLocalizedMessage());
									break;
								}
							}
						} catch (Exception e) {
						}
					}
				};
				mReadingThreads.put(index, thread);
				mSockets.put(index, socket);
				thread.start();
				if (mOnConnected != null) mOnConnected.run(mServerId, index);
			}
		}
	}

	public void setListeners(
			SocketStartedListener started,
			SocketFailedListener failed,
			SocketConnectedListener connected,
			SocketLostListener lost,
			SocketDataListener read,
			SocketDataListener write
			) {
		mOnStarted = started;
		mOnFailed = failed;
		mOnConnected = connected;
		mOnLost = lost;
		mOnRead = read;
		mOnWrite = write;
	}
	public boolean isHealthy() {
		return (mServerSocket == null) ? false : true;
	}
	public String getServerId() {
		return mServerId;
	}
	public ServerSocket getServerSocket() {
		return mServerSocket;
	}
	public Socket getSocket(long index) {
		return mSockets.get(index);
	}
	public InetSocketAddress getLocalInetSocketAddress() {
		return mLocalInetSocketAddress;
	}
	public int getBacklog() {
		return mBacklog;
	}
	public int getTimeout() {
		return mTimeout;
	}
	public void close() {
		mSockets = null;
		mReadingThreads = null;
		try {
			mServerSocket.close();
		} catch (Exception e) {
		}
		mServerSocket = null;
	}

	public int count() {
		return mSockets.size();
	}
	public String getSockets() {
		return Json.toJson(mSockets.keySet());
	}
	public boolean contains(long index) {
		return mSockets.containsKey(index);
	}

	public void remove() {
		try {
			Iterator<Entry<Long, Thread>> iterator = mReadingThreads.entrySet().iterator();
			while (iterator.hasNext()) {
				Thread thread = iterator.next().getValue();
				if (thread != null) thread = null;
			}
		} catch (Exception e) {
		}
		mReadingThreads.clear();
		try {
			Iterator<Entry<Long, Socket>> iterator = mSockets.entrySet().iterator();
			while (iterator.hasNext()) {
				Socket socket = iterator.next().getValue();
				if (socket != null) {
					try {
						socket.close();
					} catch (IOException e) {
					}
					socket = null;
				}
			}
		} catch (Exception e) {
		}
		mSockets.clear();
	}
	public void remove(long index) {
		try {
			Thread thread = mReadingThreads.get(index);
			if (thread != null) {
				thread = null;
			}
		} catch (Exception e) {
		}
		mReadingThreads.remove(index);
		try {
			Socket socket = mSockets.get(index);
			if (socket != null) {
				try {
					socket.close();
				} catch (IOException e) {
				}
				socket = null;
			}
		} catch (Exception e) {
		}
		mSockets.remove(index);
	}
	public boolean write(long index, byte[] bytes) {
		Socket socket = mSockets.get(index);
		if (socket == null) return false;
		try {
			OutputStream outputStream = socket.getOutputStream();
			outputStream.write(bytes);
			if (mOnWrite != null) mOnWrite.run(mServerId, index, bytes);
			return true;
		} catch (Exception e) {
			close();
			if (mOnLost != null) mOnLost.run(mServerId, index, e.getLocalizedMessage());
			return false;
		}
	}

}
